package com.freecloud.plug.mybatis.plugins;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
import com.freecloud.common.LoggerUtil;
import com.mchange.v1.util.SetUtils;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Field;
import java.util.Set;

/**
 * 简单分表，根据传入的主键ID，实现单数入<表名>表双数入<表名_1>表
 * @Author: maomao
 * @Date: 2021-04-09 17:28
 */
@Intercepts({
        @Signature(type = Executor.class, //表示要拦截的接口类型
                method = "update",  //表示要拦截接口的对应方法
                args = {MappedStatement.class, Object.class} //表示拦截方法对应的参数列表，反射时使用（解决方法重载问题）
        ),
        @Signature(type = Executor.class, //表示要拦截的接口类型
                method = "query",  //表示要拦截接口的对应方法
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} //表示拦截方法对应的参数列表，反射时使用（解决方法重载问题）
        )
})
public class SimpleTableInterceptor  implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Set<SqlCommandType> typeSet = CollectionUtil.newHashSet(SqlCommandType.DELETE,SqlCommandType.INSERT,SqlCommandType.UPDATE);
        if (!typeSet.contains(mappedStatement.getSqlCommandType())) {
            return invocation.proceed();
        }
        //获得执行的sql语句
        BoundSql boundSql = mappedStatement.getBoundSql(invocation.getArgs()[1]);
        //sql语法解析工具，方便获取表名
        Statement statement = CCJSqlParserUtil.parse(boundSql.getSql());
        //获取参数
        Object parameter = boundSql.getParameterObject();
        JSON parameterJson = JSONUtil.parse(parameter);

        Long id = (Long) parameterJson.getByPath("id");
        //使用ID取余数，确定执行表名
        Long tableIndex = id % 2;

        LoggerUtil.printThread("tableIndex : " + tableIndex);

        Table table;
        String newSql,newTableName = null;
        if(statement instanceof Update){
            Update update = (Update) statement;
            table = update.getTable();
            if(tableIndex == 0){
                newTableName = table.getName() + "_1";
            }else if(table.getName().lastIndexOf("_1") > 0){
                newTableName =  table.getName().replace("_1","");
            }
            if(StrUtil.isNotEmpty(newTableName)){
                table.setName(newTableName);
            }
            newSql = update.toString();
        }else{
            Insert insert = (Insert) statement;
            table = insert.getTable();
            if(tableIndex == 0){
                newTableName = table.getName() + "_1";
            }else if(table.getName().lastIndexOf("_1") > 0){
                newTableName =  table.getName().replace("_1","");
            }
            if(StrUtil.isNotEmpty(newTableName)){
                table.setName(newTableName);
            }
            newSql = insert.toString();
        }


        LoggerUtil.printThread("新sql ： " + newSql);

        // 自定义sqlSource
        SqlSource sqlSource = new StaticSqlSource(mappedStatement.getConfiguration(), newSql, boundSql.getParameterMappings());

        // 修改原来的sqlSource
        Field field = MappedStatement.class.getDeclaredField("sqlSource");
        field.setAccessible(true);
        field.set(mappedStatement, sqlSource);
        return invocation.proceed();
    }
}
